/*
 * base64.cpp
 *
 *  Created on: 2008-12-17
 *      Author: fanliangliang
 */

#include "base64.h"
#include <cstring>
#include <iostream>
#include <iomanip>

BEGIN_NAMESPACE_TOXIC

using namespace std;

const char Base64::DEFAULT_PADDING('=');

const char Base64::DEFAULT_ALPHABET[64] = {
	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
	'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
	'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
	'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
	'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
	'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
	'w', 'x', 'y', 'z', '0', '1', '2', '3',
	'4', '5', '6', '7', '8', '9', '+', '/'
};

const char Base64::DEFAULT_REVERSE_ALPHABET[128] = {
	-1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, 62, -1, -1, -1, 63,
	52, 53, 54, 55, 56, 57, 58, 59,
	60, 61, -1, -1, -1, -1, -1, -1,
	-1,  0,  1,  2,  3,  4,  5,  6,
	 7,  8,  9, 10, 11, 12, 13, 14,
	15, 16, 17, 18, 19, 20, 21, 22,
	23, 24, 25, -1, -1, -1, -1, -1,
	-1, 26, 27, 28, 29, 30, 31, 32,
	33, 34, 35, 36, 37, 38, 39, 40,
	41, 42, 43, 44, 45, 46, 47, 48,
	49, 50, 51, -1, -1, -1, -1, -1
};


Base64::Base64()
:_alphabet(DEFAULT_ALPHABET)
,_reverseAlphabet(DEFAULT_REVERSE_ALPHABET)
,_padding(DEFAULT_PADDING) {

}

Base64::~Base64() {
	destroyReverseAlphabet();
}

string Base64::encode(const string& str) {
	return encode(str.c_str(), str.size());
}

string Base64::encode(const void* input, size_t length) {
	string result;
	if (char* out = encode(input, length, 0)) {
		result.assign(out);
		delete [] out;
	}
	return result;
}

char* Base64::encode(const void* input, size_t inputLength, size_t* outputLength) {
	return encodeImplement(static_cast<const char*>(input), inputLength, outputLength);
}

string Base64::decode(const string& str) {
	return decode(str.c_str(), str.size());
}

string Base64::decode(const void* input, size_t length) {
	string result;
	size_t resultLength;
	if (char* out = decode(input, length, &resultLength)) {
		result.assign(out, resultLength);
		delete [] out;
	}
	return result;
}

char* Base64::decode(const void* input, size_t inputLength, size_t* outputLength) {
	return decodeImplement(static_cast<const char*>(input), inputLength, outputLength);
}

void Base64::setAlphabet(const char* alphabet) {
	if (alphabet == 0) {
		alphabet = DEFAULT_ALPHABET;
	}

	if (_alphabet != alphabet) {
		_alphabet = alphabet;
		destroyReverseAlphabet();
		if (_alphabet == DEFAULT_ALPHABET) {
			_reverseAlphabet = DEFAULT_REVERSE_ALPHABET;
		}
		else {
			char* reverseAlphabet = new char[128];
			memset(reverseAlphabet, -1, 128);
			for (size_t i = 0; i < 64; ++i) {
				char ch = _alphabet[i] & 0x7f;
				reverseAlphabet[size_t(ch)] = indexOf(ch, _alphabet);
			}
			_reverseAlphabet = reverseAlphabet;
		}
	}
}

void Base64::setPadding(char padding) {
	_padding = padding;
}

const char* Base64::alphabet() const {
	return _alphabet;
}

char Base64::padding() const {
	return _padding;
}

size_t Base64::indexOf(char ch, const char* alphabet) {
	const char* pos = strchr(alphabet, ch);
	return pos == 0 ? 0 : (pos - alphabet);
}

void Base64::destroyReverseAlphabet() {
	if (_reverseAlphabet != DEFAULT_REVERSE_ALPHABET) {
		delete [] _reverseAlphabet;
	}
}

bool Base64::checkDecodeInput(const char* input, size_t length) {
	for (size_t i = 0; i < length; ++i) {
		if (_reverseAlphabet[size_t(input[i])] == char(-1)) {
			return false;
		}
	}
	return true;
}

char* Base64::encodeImplement(const char* input, size_t inputLength, size_t* outputLength) {
	if (input == 0) {
		return 0;
	}

	size_t encryptedLength = ((inputLength + 2) / 3) << 2;

	// length overflow
	if (encryptedLength < inputLength) {
		return 0;
	}

	if (outputLength != 0) {
		*outputLength = encryptedLength;
	}

	char* result = new char[encryptedLength + 1];
	char* out = result;
	size_t numGroup = inputLength / 3;
	for (size_t i = 0; i < numGroup; ++i) {
		unsigned char a = input[0];
		unsigned char b = input[1];
		unsigned char c = input[2];
		*out++ = _alphabet[a >> 2];
		*out++ = _alphabet[((a & 0x03 ) << 4) | ((b & 0xf0) >> 4)];
		*out++ = _alphabet[((b & 0x0f) << 2) | ((c & 0xc0) >> 6)];
		*out++ = _alphabet[c & 0x3f];
		input += 3;
	}

	size_t numPadding = inputLength % 3;
	if (numPadding == 1) {
		unsigned char a = input[0];
		*out++ = _alphabet[a >> 2];
		*out++ = _alphabet[(a & 0x03 ) << 4];
		*out++ = _padding;
		*out++ = _padding;
	}
	else if (numPadding == 2) {
		unsigned char a = input[0];
		unsigned char b = input[1];
		*out++ = _alphabet[a >> 2];
		*out++ = _alphabet[((a & 0x03 ) << 4) | (b >> 4)];
		*out++ = _alphabet[(b & 0x0f) << 2];
		*out++ = _padding;
	}

	*out = '\0';
	return result;
}

char* Base64::decodeImplement(const char* input, size_t inputLength, size_t* outputLength) {
	// inputLength must be a multiple of 4
	if (input == 0 || inputLength & 0x03 != 0) {
		return 0;
	}

	size_t numPadding = 0;
	for (const char* inputEnd = input + inputLength; inputEnd != input && *--inputEnd == _padding; ) {
		++numPadding;
	}

	if (numPadding > 2) {
		return 0;
	}

	if (!checkDecodeInput(input, inputLength - numPadding)) {
		return 0;
	}

	size_t decryptedLength = (inputLength >> 2) * 3 - numPadding;
	if (outputLength != 0) {
		*outputLength = decryptedLength;
	}

	char* result = new char[decryptedLength + 1];
	char* out = result;
	size_t numGroup = decryptedLength / 3;
	for (size_t i = 0; i < numGroup; ++i) {
		unsigned char a = _reverseAlphabet[size_t(input[0])];
		unsigned char b = _reverseAlphabet[size_t(input[1])];
		unsigned char c = _reverseAlphabet[size_t(input[2])];
		unsigned char d = _reverseAlphabet[size_t(input[3])];
		*out++ = (a << 2) | (b >> 4);
		*out++ = ((b & 0x0f) << 4) | (c >> 2);
		*out++ = ((c & 0x03) << 6) | d;
		input += 4;
	}

	if (numPadding == 1) {
		unsigned char a = _reverseAlphabet[size_t(input[0])];
		unsigned char b = _reverseAlphabet[size_t(input[1])];
		unsigned char c = _reverseAlphabet[size_t(input[2])];
		*out++ = (a << 2) | (b >> 4);
		*out++ = ((b & 0x0f) << 4) | (c >> 2);
	}
	else if (numPadding == 2) {
		unsigned char a = _reverseAlphabet[size_t(input[0])];
		unsigned char b = _reverseAlphabet[size_t(input[1])];
		*out++ = (a << 2) | (b >> 4);
	}

	*out = '\0';
	return result;
}


void Base64::printAlphabet() const {
	cout << "const char alphabet[64] = {\n";
	for (int i = 0; i < 64; ++i) {
		if (i % 8 == 0) {
			cout << '\t';
		}

		cout << '\'' << _alphabet[i] << '\'';

		if (i != 63) {
			cout << ',';
		}

		if ((i + 1) % 8 == 0) {
			cout << '\n';
		}
		else {
			cout << ' ';
		}
	}
	cout << "};" << endl;
}

void Base64::printReverseAlphabet() const {
	cout << "const char reverseAlphabet[128] = {\n";
	for (int i = 0; i < 128; ++i) {
		if (i % 8 == 0) {
			cout << '\t';
		}

		cout << setw(2) << int(_reverseAlphabet[i]);

		if (i != 127) {
			cout << ',';
		}

		if ((i + 1) % 8 == 0) {
			cout << '\n';
		}
		else {
			cout << ' ';
		}
	}
	cout << "};" << endl;
}

END_NAMESPACE_TOXIC
